/*
* Copyright or © or Copr. Amaury Carrade (2014 - 2016)
*
* http://amaury.carrade.eu
*
* This software is governed by the CeCILL-B license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL-B
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-B license and that you accept its terms.
*/
package eu.carrade.amaury.UHCReloaded.utils;
import fr.zcraft.zlib.tools.Callback;
import fr.zcraft.zlib.tools.PluginLogger;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.FireworkEffect;
import org.bukkit.FireworkEffect.Builder;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Firework;
import org.bukkit.entity.Player;
import org.bukkit.inventory.meta.FireworkMeta;
import java.util.Random;
public class UHUtils
{
/**
* Extracts a string from a list of arguments, starting at the given index.
*
* @param args The raw arguments.
* @param startIndex The index of the first item in the returned string (first argument given: 0).
*
* @return The extracted string.
*
* @throws IllegalArgumentException if the index of the first element is out of the bounds of the arguments' list.
*/
public static String getStringFromCommandArguments(String[] args, int startIndex)
{
if (args.length < startIndex)
{
throw new IllegalArgumentException("The index of the first element is out of the bounds of the arguments' list.");
}
String text = "";
for (int index = startIndex; index < args.length; index++)
{
if (index < args.length - 1)
{
text += args[index] + " ";
}
else
{
text += args[index];
}
}
return text;
}
/**
* Converts a string to a number of seconds.
* <p>
* Format:
* <ul>
* <li><tt>mm</tt> – number of minutes;</li>
* <li><tt>mm:ss</tt> – minutes and seconds;</li>
* <li><tt>hh:mm:ss</tt> – hours, minutes and seconds.</li>
* </ul>
*
*
* @param text The text to be converted.
* @return The number of seconds represented by this string.
*
* @throws IllegalArgumentException if the text is not formatted as above.
* @throws NumberFormatException if the text between the colons cannot be converted in integers.
*/
public static int string2Time(String text)
{
String[] split = text.split(":");
if (text.isEmpty() || split.length > 3)
{
throw new IllegalArgumentException("Badly formatted string in string2time, formats allowed are mm, mm:ss or hh:mm:ss.");
}
if (split.length == 1) // "mm"
{
return Integer.valueOf(split[0]) * 60;
}
else if (split.length == 2) // "mm:ss"
{
return Integer.valueOf(split[0]) * 60 + Integer.valueOf(split[1]);
}
else // "hh:mm:ss"
{
return Integer.valueOf(split[0]) * 3600 + Integer.valueOf(split[1]) * 60 + Integer.valueOf(split[2]);
}
}
/**
* Converts a string to a number of seconds.
*
* Prints a warning if the format is invalid.
*
* @param text The text to be converted.
* @param defaultValue The default value returned if the format is invalid.
*
* @return The extracted seconds, or the default value if invalid.
* @see #string2Time(String)
*/
public static int string2Time(String text, Integer defaultValue)
{
try
{
return string2Time(text);
}
catch (IllegalArgumentException | NullPointerException e)
{
PluginLogger.warning("Invalid duration '{0}', using {1} seconds instead.", text, defaultValue);
return defaultValue;
}
}
/**
* Converts a string to a boolean.
*
* <p>
* {@code true, on, 1, yes} (case insensitive) -> {@code true}.<br />
* Anything else ({@code null} included) -> {@code false}.
* </p>
*
* @param raw The raw string.
* @return a boolean.
*/
public static boolean stringToBoolean(String raw)
{
return raw != null
&& (
raw.equalsIgnoreCase("true")
|| raw.equalsIgnoreCase("on")
|| raw.equalsIgnoreCase("1")
|| raw.equalsIgnoreCase("yes")
);
}
/**
* @param integer An integer.
* @return A string representation of this integer, with an explicit "+" if positive.
*/
public static String integerToStringWithSign(int integer)
{
return (integer < 0 ? "" : "+") + String.valueOf(integer);
}
/**
* Finds a safe spot where teleport the player, and teleport the player to that spot.
* If a spot is not found, the player is not teleported, except if {@code force} is set to true.
*
* @param player
* @param location
* @param force If true the player will be teleported to the exact given location if there is no safe spot.
* @return true if the player was effectively teleported.
*/
public static boolean safeTP(Player player, Location location, boolean force)
{
// If the target is safe, let's go
if (isSafeSpot(location))
{
player.teleport(location);
return true;
}
// If the teleportation is forced, let's go
if (force)
{
player.teleport(location);
return true;
}
Location safeSpot = searchSafeSpot(location);
// A spot was found, let's teleport.
if (safeSpot != null)
{
player.teleport(safeSpot);
return true;
}
// No spot found; the teleportation is cancelled.
else
{
return false;
}
}
/**
* Searches a safe spot where teleport the player, and teleport the player to that spot.
* If a spot is not found, the player is not teleported, except if {@code force} is set to true.
*
* @param player
* @param location
* @return true if the player was effectively teleported.
*/
public static boolean safeTP(Player player, Location location)
{
return safeTP(player, location, false);
}
/**
* Searches a safe spot in the given location.
*
* The spot is in the same X;Z coordinates.
*
* @param location The location where to find a safe spot.
* @return A Location object representing the safe spot, or null if no safe spot is available.
*/
public static Location searchSafeSpot(Location location)
{
// We try to find a spot above or below the target
Location safeSpot = null;
final int maxHeight = (location.getWorld().getEnvironment() == World.Environment.NETHER) ? 125 : location.getWorld().getMaxHeight() - 2; // (thx to WorldBorder)
for (int yGrow = location.getBlockY(), yDecr = location.getBlockY(); yDecr >= 1 || yGrow <= maxHeight; yDecr--, yGrow++)
{
// Above?
if (yGrow < maxHeight)
{
Location spot = new Location(location.getWorld(), location.getBlockX(), yGrow, location.getBlockZ());
if (isSafeSpot(spot))
{
safeSpot = spot;
break;
}
}
// Below?
if (yDecr > 1 && yDecr != yGrow)
{
Location spot = new Location(location.getWorld(), location.getX(), yDecr, location.getZ());
if (isSafeSpot(spot))
{
safeSpot = spot;
break;
}
}
}
// A spot was found, we changes the pitch & yaw according to the original location.
if (safeSpot != null)
{
safeSpot.setPitch(location.getPitch());
safeSpot.setYaw(location.getYaw());
}
return safeSpot;
}
/**
* Checks if a given location is safe.
* A safe location is a location with two breathable blocks (aka transparent block or water)
* over something solid (or water).
*
* @param location
* @return true if the location is safe.
*/
public static boolean isSafeSpot(Location location)
{
Block blockCenter = location.getWorld().getBlockAt(location.getBlockX(), location.getBlockY(), location.getBlockZ());
Block blockAbove = location.getWorld().getBlockAt(location.getBlockX(), location.getBlockY() + 1, location.getBlockZ());
Block blockBelow = location.getWorld().getBlockAt(location.getBlockX(), location.getBlockY() - 1, location.getBlockZ());
if ((blockCenter.getType().isTransparent() || (blockCenter.isLiquid() && !blockCenter.getType().equals(Material.LAVA) && !blockCenter.getType().equals(Material.STATIONARY_LAVA)))
&& (blockAbove.getType().isTransparent() || (blockAbove.isLiquid() && !blockAbove.getType().equals(Material.LAVA) && !blockCenter.getType().equals(Material.STATIONARY_LAVA))))
{
// two breathable blocks: ok
// The block below is solid, or liquid (but not lava)
return blockBelow.getType().isSolid() || blockBelow.getType().equals(Material.WATER) || blockBelow.getType().equals(Material.STATIONARY_WATER);
}
else
{
return false;
}
}
/**
* Spawns a random firework at the given location.
*
* Please note: because the power of a firework is an integer, the min/max heights
* are with a precision of ±5 blocks.
*
* @param location The location where the firework will be spawned.
* @param heightMin The minimal height of the explosion.
* @param heightMax The maximal height of the explosion.
*
* @return The random firework generated.
*/
public static Firework generateRandomFirework(Location location, int heightMin, int heightMax)
{
Random rand = new Random();
Firework firework = (Firework) location.getWorld().spawnEntity(location, EntityType.FIREWORK);
FireworkMeta meta = firework.getFireworkMeta();
int effectsCount = rand.nextInt(3) + 1;
for (int i = 0; i < effectsCount; i++)
{
meta.addEffect(generateRandomFireworkEffect());
}
// One level of power is half a second of flight time.
// In half a second, a firework fly ~5 blocks.
// So, one level of power = ~5 blocks.
meta.setPower((int) Math.min(Math.floor((heightMin / 5) + rand.nextInt(heightMax / 5)), 128D));
firework.setFireworkMeta(meta);
return firework;
}
/**
* Generates a random firework effect.
*
* @return The firework effect.
*/
private static FireworkEffect generateRandomFireworkEffect()
{
Random rand = new Random();
Builder fireworkBuilder = FireworkEffect.builder();
int colorCount = rand.nextInt(3) + 1;
int trailCount = rand.nextInt(3) + 1;
fireworkBuilder.flicker(rand.nextInt(3) == 1);
fireworkBuilder.trail(rand.nextInt(3) == 1);
for (int i = 0; i < colorCount; i++)
{
fireworkBuilder.withColor(generateRandomColor());
}
for (int i = 0; i < trailCount; i++)
{
fireworkBuilder.withFade(generateRandomColor());
}
// Random shape
FireworkEffect.Type[] types = FireworkEffect.Type.values();
fireworkBuilder.with(types[rand.nextInt(types.length)]);
return fireworkBuilder.build();
}
/**
* Generates a random color.
*
* @return The color.
*/
private static Color generateRandomColor()
{
Random rand = new Random();
return Color.fromBGR(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256));
}
/**
* Calls a callback if it is defined (non-{@code null}).
*
* @param callback The callback
* @param argument The callback's argument.
* @param <T> The callback's argument type.
*/
public static <T> void callIfDefined(Callback<T> callback, T argument)
{
if (callback != null) callback.call(argument);
}
/**
* Returns the overworld.
*
* @return the... overworld?
*/
public static World getOverworld()
{
for (World world : Bukkit.getServer().getWorlds())
{
if (world.getEnvironment() != World.Environment.NETHER && world.getEnvironment() != World.Environment.THE_END)
{
return world;
}
}
return null;
}
}